#include <windows.h>
#include <mmsystem.h>
//#include <mmddk.h>

#include <dsound.h>
#include <dshow.h>
#include <comdef.h>
#include <string>
#include "system.h"
#include "shared\stlex.h"
#include "shared\dbg.h"

#pragma comment(lib, "strmiids")

namespace avlib{
System::System(){
}

System::~System(){
	delete_vector(mAudioDevices);
	delete_vector(mVideoDevices);
	//CoUninitialize();

}
/**
*** enum system wavein device,
*** return: device count
**/
#if 0
int System::enum_wavein(){

	UINT count = waveInGetNumDevs();

	assert(count == mWaveInDevices.size());
	return count;

	//see http://stackoverflow.com/questions/1929658/is-it-possible-to-relate-the-same-audio-device-in-different-apis

	for(UINT i=0;i<count;i++){

#if 1
		WAVEINCAPS2 caps;
		MMRESULT res = waveInGetDevCaps(i,(LPWAVEINCAPSA)&caps,sizeof(caps));
		if(res == MMSYSERR_NOERROR){

			WaveInDevices::iterator it = mWaveInDevices.begin();
			while(it != mWaveInDevices.end()){

				it++;
				//if((*it)->dev_guid == caps.

			}

			log("enum_wavein,%s", caps.szPname);
		}else{
			log("Warning: waveInGetDevCaps %d",res);
		}
#else

		//we get a GUID,not readable
		size_t sz;
		//Get the size (including the terminating null) of
		// the endpoint ID string of the waveOut device.
		MMRESULT res = waveInMessage((HWAVEIN)IntToPtr(i),
			DRV_QUERYFUNCTIONINSTANCEIDSIZE,
			(DWORD_PTR)&sz, NULL);
		if (res == MMSYSERR_NOERROR){

			char *tmp = (char*)malloc(sz);
			res = waveOutMessage((HWAVEOUT)IntToPtr(i),
				DRV_QUERYFUNCTIONINSTANCEID,
				(DWORD_PTR)tmp,
				sz);
			if(res ==  MMSYSERR_NOERROR){
				WaveInDevice *dev = new WaveInDevice();
				dev->dev_id = i;
				dev->dev_name = tmp;//only 32 char
			}
			delete(tmp);

		}else{
			log("waveInMessage error %d",res);
		}

#endif
	}
	return 0;
}

//directsound way 
void System::Init(){

	if(FAILED(CoInitialize(NULL))){
		log("CoInitializee error");
		return;
	}

	HRESULT hr = DirectSoundCaptureEnumerate(dsEnumCallback,this);
	if(hr != DS_OK){
		log("DirectSoundCaptureEnumerate error %d",hr);
	}


}


BOOL CALLBACK dsEnumCallback(LPGUID lpGuid,
							 LPCSTR lpcstrDescription,
							 LPCSTR lpcstrModule,
							 LPVOID lpContext
							 ){
								 static int iTimes = 0;

								 if(lpGuid == 0) return TRUE;

								 System *sys = (System *)lpContext;

								 WaveInDevice *dev = new WaveInDevice();
								 dev->dev_id = iTimes++;
								 //dev->dev_id = 0xffff;
								 dev->dev_guid = *lpGuid;
								 dev->dev_name = lpcstrDescription;//only 32 cha
								 log("enum directsound capture device,%s", lpcstrDescription);
								 sys->mWaveInDevices.push_back(dev);
								 return TRUE;
}


#endif


HRESULT EnumerateDevices(REFGUID category, IEnumMoniker **ppEnum)
{
	//clears all
	

	// Create the System Device Enumerator.
	ICreateDevEnum *pDevEnum;
	HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,  
		CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));

	if (SUCCEEDED(hr))
	{
		// Create an enumerator for the category.
		hr = pDevEnum->CreateClassEnumerator(category, ppEnum, 0);
		if (hr == S_FALSE)
		{
			hr = VFW_E_NOT_FOUND;  // The category is empty. Treat as an error.
		}
		pDevEnum->Release();
	}
	return hr;
}

void System::GetDeviceInformation(IEnumMoniker *pEnum)
{
	IMoniker *pMoniker = NULL;
	CaptureDevice *dev = NULL;

	std::string devname ;

	while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
	{
		IPropertyBag *pPropBag;
		HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
		if (FAILED(hr)){
			pMoniker->Release();
			continue;  
		} 

		VARIANT var;
		VariantInit(&var);


		dev = new CaptureDevice();

		hr = pPropBag->Read(L"CLSID", &var, 0);
		if (SUCCEEDED(hr)){

			// log("Device path: %S\n", var.bstrVal);
			//dev->dev_guid =  GUIDFromString(var.bstrVal);
			hr = CLSIDFromString(var.bstrVal,&dev->dev_guid);

			VariantClear(&var); 
		}

		// Get description or friendly name.
		hr = pPropBag->Read(L"Description", &var, 0);
		if (FAILED(hr))
		{
			hr = pPropBag->Read(L"FriendlyName", &var, 0);
		}
		if (SUCCEEDED(hr))
		{
			dev->dev_name = _bstr_t(var.bstrVal);
			log("video device: %s",dev->dev_name.c_str());
			VariantClear(&var); 
		}
		//hr = pPropBag->Write(L"FriendlyName", &var);

		// WaveInID applies only to audio capture devices.   
		hr = pPropBag->Read(L"WaveInID", &var, 0);
		if (SUCCEEDED(hr))
		{
			log("WaveIn ID: %d\n", var.lVal);
			assert(is_audio_device && dev);
			dev->dev_id = var.lVal;
			VariantClear(&var); 
		}

		hr = pPropBag->Read(L"DevicePath", &var, 0);
		if (SUCCEEDED(hr)){
			// The device path is not intended for display.
			log("Device path: %S\n", var.bstrVal);
			VariantClear(&var); 
		}

		pPropBag->Release();

		if(is_audio_device){
			mAudioDevices.push_back(dev);
		}else{
			mVideoDevices.push_back(dev);
		}

		pMoniker->Release();
	}
}

/*void main()
{
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
IEnumMoniker *pEnum;

hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &pEnum);
if (SUCCEEDED(hr))
{
DisplayDeviceInformation(pEnum);
pEnum->Release();
}
hr = EnumerateDevices(CLSID_AudioInputDeviceCategory, &pEnum);
if (SUCCEEDED(hr))
{
DisplayDeviceInformation(pEnum);
pEnum->Release();
}
CoUninitialize();
}

IBaseFilter *pCap = NULL;
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap);
if (SUCCEEDED(hr))
{
hr = m_pGraph->AddFilter(pCap, L"Capture Filter");
}

}*/

// http://msdn.microsoft.com/en-us/library/windows/desktop/dd377566(v=vs.85).aspx

bool System::EnumDevices(){
	/*HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
	if (FAILED(hr)){
		log("CoInitializeEx error");
		return false;
	}*/

//clear all
	delete_vector(mAudioDevices);
	delete_vector(mVideoDevices);


	
	/*CaptureDevice *adev = new CaptureDevice();
	adev->dev_id  = 0;
	adev->dev_name = "Desktop Screen";
	mVideoDevices.push_back(adev);*/


	HRESULT hr = S_OK;
	//enum video
	is_audio_device = false;
	IEnumMoniker *pEnum = NULL;
	hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &pEnum);
	if (FAILED(hr)){
		log("EnumerateDevices error");
		return false;
	}

	GetDeviceInformation(pEnum);
	pEnum->Release();

	//enum audio
	is_audio_device = true;
	hr = EnumerateDevices(CLSID_AudioInputDeviceCategory, &pEnum);
	if (SUCCEEDED(hr))
	{
		GetDeviceInformation(pEnum);
		pEnum->Release();
	}

	return true;
}
};